﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using PSE.Framework.Common.Validation;
using PSE.Framework.Service.ExceptionHandling;

namespace PSE.Framework.Service.Validation.BusinessRules
{
    /// <summary>
    /// Validador de regras de negocio
    /// </summary>
    public class BusinessRulesValidator
    {
        #region [[ Atributos ]]

        public bool StopOnFirstValidationError { get; set; }
        public bool HasErrors { get; set; }
        private Dictionary<string, IBusinessRuleValidation> _validatorList;
        private Dictionary<string, List<ValidationError>> _errorList;

        #endregion

        #region [[ Construtores ]]

        public BusinessRulesValidator()
        {
            this._validatorList = new Dictionary<string, IBusinessRuleValidation>();
            this._errorList = new Dictionary<string, List<ValidationError>>();

            this.StopOnFirstValidationError = true;
        }

        #endregion

        #region [[ Metodos ]]


        /// <summary>
        /// Adiciona uma classe de validação ao Validador.
        /// </summary>
        /// <param name="key">Chave usada para identificar a regra de validação (deve ser única)</param>
        /// <param name="validator">Classe de validação</param>
        public void AddValidation(string key, IBusinessRuleValidation validator)
        {
            this.ValidateKey(key);

            if (validator == null)
                throw new ArgumentNullException("validator");

            if (this._validatorList.ContainsKey(key))
                throw new Exception("Identification key already exists");
            else
                this._validatorList.Add(key, validator);
        }


        /// <summary>
        /// Remove uma classe de validação do Validador.
        /// </summary>
        /// <param name="key">Chave usada para identificar a regra de validação </param>
        public void RemoveValidation(string key)
        {
            this.ClearErrorsAt(key);
            this._validatorList.Remove(key);
        }


        /// <summary>
        /// Remove todas as classes de validação do Validador.
        /// </summary>
        public void RemoveAllValidations()
        {
            this._validatorList.Clear();
            this._errorList.Clear();
            this.HasErrors = false;
        }


        /// <summary>
        /// Aciona cada uma das classes de validação contidas no Validator. 
        /// Caso ocorra um ou mais erros, o retorno será false.
        /// Caso a propriedade StopOnFirstError esteja acionada, a validação será encerrada no primeiro erro.
        /// </summary>
        /// <returns></returns>
        public bool IsAllValid()
        {
            int errorsCount = 0;

            foreach (KeyValuePair<string, IBusinessRuleValidation> item in this._validatorList)
            {
                if (this.StopOnFirstValidationError && errorsCount > 0)
                    return false;

                item.Value.Validate();

                List<ValidationError> errors = item.Value.GetErrors();
                this.StoreErrors(item.Key, errors);
                errorsCount += errors.Count;
                errors.Clear();
            }

            return (errorsCount == 0);
        }


        /// <summary>
        /// Aciona cada a classe de validação identificada pela chave.
        /// Caso ocorra um ou mais erros, o retorno será false.
        /// </summary>
        /// <param name="key">Chave usada para identificar a regra de validação</param>
        /// <returns></returns>
        public bool IsValidAt(string key)
        {
            this.ValidateKey(key);
            this.ValidateKeyInValidationList(key);

            IBusinessRuleValidation item = this._validatorList[key];
            item.Validate();

            List<ValidationError> errors = item.GetErrors();
            this.StoreErrors(key, errors);
            bool valid = (errors.Count == 0);
            errors.Clear();

            return valid;
        }


        /// <summary>
        /// Aciona cada uma das classes de validação contidas no Validator. 
        /// Caso ocorra um ou mais erros, será lançada uma ValidationException.
        /// Caso a propriedade StopOnFirstError esteja acionada, a validação será encerrada no primeiro erro.
        /// </summary>
        public void Validate()
        {
            if (!this.IsAllValid())
            {
                ValidationException ex = new ValidationException();

                foreach (string key in this.GetAllErrors().Keys)
                {
                    this.AddErrorIntoException(this.GetErrorsAt(key), ex);
                }

                throw ex;
            }
        }

        /// <summary>
        /// Verifica se houve erro de validação para uma regra especifica
        /// Necessario ter executado IsAllValid() ou IsValidAt() anteriormente.
        /// </summary>
        /// <param name="key">Chave usada para identificar a regra de validação</param>
        /// <returns></returns>
        public bool HasErrorsAt(string key)
        {
            this.ValidateKey(key);
            this.ValidateKeyInValidationList(key);

            return (this._errorList[key].Count > 0);
        }


        /// <summary>
        /// Retorna todos os erros de validação, agrupados pelas chaves de identificação
        /// Necessario ter executado IsAllValid() ou IsValidAt() anteriormente.
        /// </summary>
        /// <returns></returns>
        public Dictionary<string, List<ValidationError>> GetAllErrors()
        {
            return (this._errorList.Count > 0 ? this._errorList : null);
        }


        /// <summary>
        /// Retorna os erros de validação para uma regra especifica
        /// Necessario ter executado IsAllValid() ou IsValidAt() anteriormente.
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        public List<ValidationError> GetErrorsAt(string key)
        {
            this.ValidateKey(key);
            this.ValidateKeyInValidationList(key);

            if (this._errorList.ContainsKey(key) && this._errorList[key].Count > 0)
                return this._errorList[key];
            else
                return null;
        }


        /// <summary>
        /// Limpa a lista de erros de todas as validações
        /// Só haverá erros se executado IsAllValid() ou IsValidAt() anteriormente.
        /// </summary>
        public void ClearAllErrors()
        {
            this._errorList.Clear();
            this.HasErrors = false;
        }


        /// <summary>
        /// Limpa a lista de erros de uma validação especifica.
        /// Só haverá erros se executado IsAllValid() ou IsValidAt() anteriormente.
        /// </summary>
        /// <param name="key"></param>
        public void ClearErrorsAt(string key)
        {
            this.ValidateKey(key);
            this.ValidateKeyInValidationList(key);

            if (this._errorList.ContainsKey(key))
                this._errorList.Remove(key);

            if (this._errorList.Values.Count == 0)
                this.HasErrors = false;
        }

        /// <summary>
        /// Armazena os erros ocorridos em uma classe de validação de negocio.
        /// </summary>
        /// <param name="key">Chave usada para identificar a regra de validação</param>
        /// <param name="errorList">Lista de erros de validação</param>
        private void StoreErrors(string key, List<ValidationError> errorList)
        {
            if (errorList != null && errorList.Count > 0)
            {
                this.HasErrors = true;

                if (!this._errorList.ContainsKey(key))
                    this._errorList.Add(key, new List<ValidationError>());

                this._errorList[key].AddRange(errorList);
            }
        }


        /// <summary>
        /// Valida se a key informada é válida
        /// </summary>
        /// <param name="key"></param>
        private void ValidateKey(string key)
        {
            if (string.IsNullOrEmpty(key))
                throw new ArgumentNullException("key", "The business rule requires an identification key");
        }

        /// <summary>
        /// Valida se a key informada consta na lista de validações
        /// </summary>
        /// <param name="key"></param>
        private void ValidateKeyInValidationList(string key)
        {
            if (!this._validatorList.ContainsKey(key))
                throw new ArgumentException(string.Format("Invalid identification key '{0}'", key), "key");
        }

        /// <summary>
        /// Adiciona cada um dos códigos de exceção a lista do ValidationException com suporte a criação da tradução
        /// </summary>
        /// <param name="errorList">Lista de Erros</param>
        /// <param name="validationException">Exceção</param>
        private void AddErrorIntoException(List<ValidationError> errorList, ValidationException validationException)
        {
            foreach (ValidationError error in errorList)
            {
                validationException.AddError(error.ErrorNumber);
            }
        }


        /// <summary>
        /// Adiciona uma classe de validação ao Validador.
        /// </summary>
        /// <param name="validator">Classe de validação (A key será o Type da Classe)</param>
        public void AddValidation(IBusinessRuleValidation validator)
        {
            if (validator == null)
                throw new ArgumentNullException("validator");

            string key = validator.GetType().Name;

            if (this._validatorList.ContainsKey(key))
                throw new Exception("Identification key already exists");
            else
                this._validatorList.Add(key, validator);
        }

        /// <summary>
        /// Retorna a classe de validação
        /// A key será o type da classe
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        public T GetValidation<T>() where T : BusinessRules.IBusinessRuleValidation
        {
            string key = typeof(T).Name;
            this.ValidateKey(key);
            this.ValidateKeyInValidationList(key);
            return (T)this._validatorList[key];
        }

        #endregion

    }
}